package bean.managed;

import entity.ShippingOrderEntity;
import bean.managed.util.JsfUtil;
import bean.session.ShippingOrderEntityFacade;

import entity.BillingRequestEntity;
import entity.DepartmentEntity;
import entity.SalesOrderEntity;
import entity.SystemUserEntity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.faces.model.SelectItem;
import managedbean.AbstractBacking;
import type.InternalStatus;
import type.ShippingOrderStatus;
import util.Fixtures.Department;

@ManagedBean(name = "shippingOrderEntityController")
@SessionScoped
public class ShippingOrderEntityController extends AbstractBacking implements Serializable {

    private ShippingOrderEntity current;
    private Collection<ShippingOrderEntity> allShippingOrders;
    private Collection<ShippingOrderEntity> allShippingOrdersForWarehouse;
    private ShippingOrderEntity[] shippingOrdersToBill;
    @ManagedProperty(value = "#{salesOrderEntityController}")
    private SalesOrderEntityController salesOrderEntityController;
    @ManagedProperty(value = "#{systemMessageController}")
    private SystemMessageController systemMessageController;
    @EJB
    private bean.session.ShippingOrderEntityFacade ejbFacade;
    @EJB
    private bean.session.BillingRequestEntityFacade billingRequestFacade;
    @EJB
    private bean.session.DepartmentEntityFacade departmentFacade;
    @EJB
    private bean.session.SalesOrderEntityFacade salesOrderFacade;

    public ShippingOrderEntityController() {
    }

    public ShippingOrderEntity getSelected() {
        if (current == null) {
            current = new ShippingOrderEntity();
        }
        return current;
    }

    private ShippingOrderEntityFacade getFacade() {
        return ejbFacade;
    }

    public String prepareList() {
        recreateModel();
        return "List";
    }

    public String prepareView() {
        return "View";
    }

    public String prepareCreate() {
        return "Create";
    }

    public String create() {
        try {
            getFacade().create(current);
            JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Bundle").getString("ShippingOrderEntityCreated"));
            return prepareCreate();
        } catch (Exception e) {
            JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Bundle").getString("PersistenceErrorOccured"));
            return null;
        }
    }

    public String prepareEdit() {
        return "Edit";
    }

    public String update() {
        try {
            getFacade().edit(current);
            JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Bundle").getString("ShippingOrderEntityUpdated"));
            return "View";
        } catch (Exception e) {
            JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Bundle").getString("PersistenceErrorOccured"));
            return null;
        }
    }

    public String destroy() {
        performDestroy();
        recreateModel();
        return "List";
    }

    public String destroyAndView() {
        performDestroy();
        recreateModel();
        return "List";
    }

    private void performDestroy() {
        try {
            getFacade().remove(current);
            JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Bundle").getString("ShippingOrderEntityDeleted"));
        } catch (Exception e) {
            JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Bundle").getString("PersistenceErrorOccured"));
        }
    }

    private void recreateModel() {
        current = null;
    }

    public SelectItem[] getItemsAvailableSelectMany() {
        return JsfUtil.getSelectItems(ejbFacade.findAll(), false);
    }

    public SelectItem[] getItemsAvailableSelectOne() {
        return JsfUtil.getSelectItems(ejbFacade.findAll(), true);
    }

    @FacesConverter(forClass = ShippingOrderEntity.class)
    public static class ShippingOrderEntityControllerConverter implements Converter {

        public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
            if (value == null || value.length() == 0) {
                return null;
            }
            ShippingOrderEntityController controller = (ShippingOrderEntityController) facesContext.getApplication().getELResolver().
                    getValue(facesContext.getELContext(), null, "shippingOrderEntityController");
            return controller.ejbFacade.find(getKey(value));
        }

        java.lang.Long getKey(String value) {
            java.lang.Long key;
            key = Long.valueOf(value);
            return key;
        }

        String getStringKey(java.lang.Long value) {
            StringBuffer sb = new StringBuffer();
            sb.append(value);
            return sb.toString();
        }

        public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
            if (object == null) {
                return null;
            }
            if (object instanceof ShippingOrderEntity) {
                ShippingOrderEntity o = (ShippingOrderEntity) object;
                return getStringKey(o.getId());
            } else {
                throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + ShippingOrderEntityController.class.getName());
            }
        }
    }

    public Collection<ShippingOrderEntity> getAllShippingOrders() {
        allShippingOrders = salesOrderEntityController.getSelected().getShippingOrder();
//        System.out.append("total shipping orders: " + allShippingOrders.size());
        return allShippingOrders;
    }

    public void setAllShippingOrders(Collection<ShippingOrderEntity> allShippingOrders) {
        this.allShippingOrders = allShippingOrders;
    }

    public SalesOrderEntityController getSalesOrderEntityController() {
        return salesOrderEntityController;
    }

    public void setSalesOrderEntityController(SalesOrderEntityController salesOrderEntityController) {
        this.salesOrderEntityController = salesOrderEntityController;
    }

    public ShippingOrderEntity getCurrent() {
        return current;
    }

    public void setCurrent(ShippingOrderEntity current) {
        this.current = current;
    }

    public ShippingOrderEntity[] getShippingOrdersToBill() {
        return shippingOrdersToBill;
    }

    public void setShippingOrdersToBill(ShippingOrderEntity[] shippingOrdersToBill) {
        System.out.println("setting shipping orders to bill: " + shippingOrdersToBill == null ? null : shippingOrdersToBill.length);
        this.shippingOrdersToBill = shippingOrdersToBill;
    }

    public SystemMessageController getSystemMessageController() {
        return systemMessageController;
    }

    public void setSystemMessageController(SystemMessageController systemMessageController) {
        this.systemMessageController = systemMessageController;
    }

    public void generateBillingRequests() {
        System.out.println("Generating billing request for selected shipping orders: " + shippingOrdersToBill == null ? null : shippingOrdersToBill.length);


        SalesOrderEntity salesOrder = (SalesOrderEntity) shippingOrdersToBill[0].getSalesOrder();
        //check sale order status
        if (salesOrder.getInternalStatus() == InternalStatus.CreditCheckPending) {
            JsfUtil.addErrorMessage("Sales Order credit check pending. Please perform credit check first!");
            shippingOrdersToBill = null;
            return;
        }

        //check if chosen shipping orders already have billing requests OR has not yet been fulfilled       
        Iterator<ShippingOrderEntity> toBill = Arrays.asList(shippingOrdersToBill).iterator();
        int index = 0;
        while (toBill.hasNext()) {
            ShippingOrderEntity shipping = toBill.next();
            if (shipping.getStatus()!=ShippingOrderStatus.fulfilled) {
                shippingOrdersToBill[index] = null;
                JsfUtil.addSuccessMessage("Shipping Order #" + shipping.getId()
                        + " has not yet been fulfilled. Excluded from generation.");
            }
            else if (shipping.getBillingRequest() != null) {
                shippingOrdersToBill[index] = null;
                JsfUtil.addSuccessMessage("Shipping Order #" + shipping.getId()
                        + " already has a billing request. Excluded from generation.");
            }
            index++;
        }

        List<ShippingOrderEntity> cleanedShippingOrdersToBill = new ArrayList<ShippingOrderEntity>();
        for (ShippingOrderEntity shipping : shippingOrdersToBill) {
            if (shipping != null) {
                cleanedShippingOrdersToBill.add(shipping);
            }
        }

        //no outstanding shipping orders available to generate billing reqeust
        if (cleanedShippingOrdersToBill.isEmpty()) {
            shippingOrdersToBill = null;
            return;
        }

        //verify if total can be covered by cash-in-advance payment
        double total = 0;
        for (ShippingOrderEntity shipping : cleanedShippingOrdersToBill) {
            total += shipping.getPackingOrder().getTotalPrice();
        }
        if (salesOrder.getCashPayableAccountedFor() == null && !(salesOrder.getCashPayable() < total)) {
            JsfUtil.addSuccessMessage("Requested total amount can be covered by Cash-in-Advance payment. Please select/wait for more shipments to request for billing.");
            shippingOrdersToBill = null;
            return;
        }

        //Create billing request
        BillingRequestEntity billingRequest = new BillingRequestEntity();
        billingRequest.setShippingOrders(cleanedShippingOrdersToBill);
        billingRequest.setSalesOrder(salesOrder);

        billingRequest.setRequestedAmount(total);

        billingRequest.created(getLoginPerson());
        billingRequestFacade.create(billingRequest);

        //update shipping orders
        for (ShippingOrderEntity shipping : cleanedShippingOrdersToBill) {
            shipping.setBillingRequest(billingRequest);
            shipping.modified(getLoginPerson());
            ejbFacade.edit(shipping);
        }

        //update sales order
        salesOrder.getBillingRequests().add(billingRequest);
        salesOrder.setInternalStatus(InternalStatus.BillingConfirmaPending);
        salesOrder.modified(getLoginPerson());
        salesOrderFacade.edit(salesOrder);

        //inform AR manager
        DepartmentEntity accountsReceivable = departmentFacade.findDepartmentByName(Department.AccountsReceivable.getName());
        SystemUserEntity accountsReceivableManager = accountsReceivable.getManager();
        String message = "Please issue invoice to customer " + salesOrder.getCustomer().getFullname()
                + " for Sales Order #" + salesOrder.getId() + ", Billing Request #" + billingRequest.getId();
        systemMessageController.getEjbFacade().createAndSendSystemMessage(accountsReceivableManager,
                message, "/entities/billingRequestEntity/ListNormal.xhtml", systemMessageController);

        JsfUtil.addSuccessMessage("Billing request filed. #" + billingRequest.getId());
    }

    public void markUnfulfilled() {
        current.setStatus(ShippingOrderStatus.pending);
        current.modified(getLoginPerson());
        update();
    }

    public void markFulfilled() {
        current.setStatus(ShippingOrderStatus.fulfilled);
        current.modified(getLoginPerson());
        update();

        //inform sales about fulfillment of shipping order
        String completion = "Shipping order #" + current.getId() 
                + " for Sales Order #" + current.getSalesOrder().getId() + " is fulfilled.";
        systemMessageController.getEjbFacade().createAndSendSystemMessage(
                current.getSalesOrder().getSystemUser(), completion,
                "/entities/salesOrderEntity/List.xhtml",
                systemMessageController);
        
        //check if a sales order's all delivery has been fulfilled
        SalesOrderEntity salesOrder = current.getSalesOrder();
        if(salesOrder.allShipmentsFulfilled()){
            salesOrder.setInternalStatus(InternalStatus.BillingRequestPending);
            salesOrder.modified(getLoginPerson());
            salesOrderFacade.edit(salesOrder);
        }
    }

    public boolean isFulfilled() {
        return current.getStatus() == ShippingOrderStatus.fulfilled;
    }

    public Collection<ShippingOrderEntity> getAllShippingOrdersForWarehouse() {
        allShippingOrdersForWarehouse = ejbFacade.findAll();
        return allShippingOrdersForWarehouse;
    }

    public void setAllShippingOrdersForWarehouse(Collection<ShippingOrderEntity> allShippingOrdersForWarehouse) {
        this.allShippingOrdersForWarehouse = allShippingOrdersForWarehouse;
    }
}
